/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFGanaPre
  \brief The ana part of the offline ana GUI system
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-05-12
  \date 2024-05-14 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2024-05-12   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <libgen.h> // for basename
#include <cstdlib>
#include <iostream>
#include <unistd.h>
#include <climits> // for UINT_MAX
#include <TSystem.h>
#include <TGProgressBar.h>
#include <TGLayout.h>
#include <TGButton.h>
#include <TGTextEntry.h>
#include <TGLabel.h>
#include <TGFileDialog.h>

#include "ETFRun.h"
#include "ETFParaManager.h"
#include "ETFTrackManager.h"
#include "ETFTaTracker.h"
#include "ETFPostMagTracker.h"
#include "ETFGlobalTracker.h"
#include "ETFPIDManager.h"
#include "ETFSource.h"
#include "ETFPID.h"
#include "ETFMath.h"
#include "ETFUnpacker.h"
#include "ETFMsg.h"

#include "ETFGanaPre.h"
#include "ETFGana.h"
#include "ETFGPrompt.h"

using std::cout;
using std::endl;

#define er ETFMsg::Error
#define form ETFMsg::Form

ETFGanaPre::ETFGanaPre(ETFGana *p, int w, int h)
 : TGCompositeFrame(p, w, h, kHorizontalFrame), fWidth(w), fHeight(h),
fNofTrkL{{0}}, fCnt{{0}}, fAnaSta(0), fFile0Size(0), fGana(p), fAnaLevel(0),
fNofEv(UINT_MAX), fSrc(0){}

ETFGanaPre::~ETFGanaPre(){}

// creaet all the graphical objects
void ETFGanaPre::Initialize(){
  fpm = ETFParaManager::Instance();
  fRun = ETFRun::Instance();

  // supposed for layout of group-frames
  auto lf = new TGLayoutHints(kLHintsCenterX | kLHintsCenterY
    | kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2);
  auto lx = new TGLayoutHints(kLHintsCenterX | kLHintsCenterY
    | kLHintsExpandX, 2, 2, 2, 2);
  auto lleft = new TGLayoutHints(kLHintsLeft | kLHintsCenterY, 2, 2, 2, 2);
  auto lfix = new TGLayoutHints(kLHintsCenterX | kLHintsCenterY | kLHintsExpandX
    | kLHintsLeft, 2, 2, 2, 2);

  // divide the frame into left and right //
  auto leftPad = new TGCompositeFrame(this, 300, 300, kFixedWidth | kFixedHeight);
  AddFrame(leftPad, lx);
  auto inputPad = new TGGroupFrame(leftPad, "User Inputs");
  inputPad->SetTitlePos(TGGroupFrame::kLeft);
  leftPad->AddFrame(inputPad, lf);
  auto rightPad = new TGCompositeFrame(this, 300, 290, kFixedHeight);
  AddFrame(rightPad, lx);

  //--- add data files ---//
  // the button
  auto addfPad = new TGCompositeFrame(inputPad, 300, 250, kHorizontalFrame);
  inputPad->AddFrame(addfPad, lf);
  fAddfBtn = new TGTextButton(addfPad, "  Add  \n  &Raw  \n  Data \n  File ", -1,
    TGTextButton::GetDefaultGC()(), TGTextButton::GetDefaultFontStruct(),
    kRaisedFrame | kDoubleBorder | kFixedWidth);
  fAddfBtn->SetWidth(70);
  fAddfBtn->Connect("Clicked()", "ETFGanaPre", this, "AddRawDataFile()");
  fAddfBtn->ChangeBackground(ETFGana::pink());
  addfPad->AddFrame(fAddfBtn, lleft);
  // the file-list label
  fDataFileLabel = new TGLabel(addfPad, "Input Data Files:\n\n");
  fDataFileLabel->SetHeight(300);
  addfPad->AddFrame(fDataFileLabel, lfix);
  auto &vDataFile = fpm->fDataFiles;
  if(vDataFile.size()){ // show the existing data files (input from terminal)
    fFile0Size = ETFMath::filesize(vDataFile[0]);
    string txt = "Input Data Files:";
    for(auto p : vDataFile) txt += '\n' + p;
    fDataFileLabel->SetText(txt.data());
    fDataFileLabel->Resize(fDataFileLabel->GetDefaultSize());
  } // end if

  //--- set event range ---//
  auto evRngFrm = new TGHorizontalFrame(inputPad, 300, 50);
  inputPad->AddFrame(evRngFrm, lf);
  // the labels
  // the text entries
  TGTextBuffer *tbufEv0 = new TGTextBuffer(10);
  tbufEv0->AddText(0, form("%u", fpm->ev0()));
  TGTextBuffer *tbufEv1 = new TGTextBuffer(10);
  tbufEv1->AddText(0, form("%u", fpm->ev1()));
  fEntryEv0 = new TGTextEntry(evRngFrm, tbufEv0);
  fEntryEv1 = new TGTextEntry(evRngFrm, tbufEv1);
  // add them to the frame
  evRngFrm->AddFrame(new TGLabel(evRngFrm, "Event Range: "), lx);
  evRngFrm->AddFrame(fEntryEv0, lx);
  evRngFrm->AddFrame(new TGLabel(evRngFrm, "to"), lx);
  evRngFrm->AddFrame(fEntryEv1, lx);

  //--- specify ana depth ---//
  auto anaDepthPad = new TGGroupFrame(inputPad, "Analysis Depth");
  anaDepthPad->SetHeight(100);
  inputPad->AddFrame(anaDepthPad, lfix);
  anaDepthPad->SetTitlePos(TGGroupFrame::kLeft);
  fC2DTrk = new TGCheckButton(anaDepthPad, new TGHotString("2D Tracking"));
  fC3DTrk = new TGCheckButton(anaDepthPad, new TGHotString("Post-Mag 3D Tracking"));
  fC3DTrk->SetToolTipText("3D tracking for  after the Dipole Magnet");
  fCPID = new TGCheckButton(anaDepthPad, new TGHotString("Post-Target PID"));
  fCPID->SetToolTipText("PID for reaction products after the reaction target");
  anaDepthPad->AddFrame(fC2DTrk, lleft);
  anaDepthPad->AddFrame(fC3DTrk, lleft);
  anaDepthPad->AddFrame(fCPID, lleft);
  // set the default input state according to user input file (mis.yaml)
  bool c2dtrk = false, c3dtrk = false, cpid = false;
  fAnaLevel = fpm->anaLevel();
  if(fAnaLevel){ // tracking enabled
    c2dtrk = true;
    c3dtrk = fpm->is3DTrk();
    if(2 == fAnaLevel) cpid = true;
  } // end else
  fC2DTrk->SetState(c2dtrk ? kButtonDown : kButtonUp);
  fC3DTrk->SetState(c3dtrk ? kButtonDown : kButtonUp);
  fCPID->SetState(cpid ? kButtonDown : kButtonUp);

  // the progress bar //
  auto prgFrm = new TGHorizontalFrame(inputPad, 300, 40);
  inputPad->AddFrame(prgFrm, lf);
  fStartAnaBtn = new TGTextButton(prgFrm, " &Start\n Ana ", -1,
    TGTextButton::GetDefaultGC()(), TGTextButton::GetDefaultFontStruct(),
    kRaisedFrame | kDoubleBorder | kFixedWidth);
  fStartAnaBtn->SetWidth(70);
  fStartAnaBtn->ChangeBackground(ETFGana::yellow());
  fStartAnaBtn->Connect("Clicked()", "ETFGanaPre", this, "StartAna()");
  prgFrm->AddFrame(fStartAnaBtn, lleft);
  fPrgBar = new TGHProgressBar(prgFrm, TGProgressBar::kFancy, 400);
  prgFrm->AddFrame(fPrgBar, lfix);
  fPrgBar->SetBarColor("green");
  fPrgBar->Reset();
  fPrgBar->ShowPosition(true, false, "%0.2f%%");

  //--- show ana result status ---//
  auto anaStaPad = new TGGroupFrame(rightPad, "Analysis Results");
  rightPad->AddFrame(anaStaPad, lf);
  anaStaPad->SetLayoutManager(new TGMatrixLayout(anaStaPad, 0, 5, 10));
  // the first row: number of analyzed events //
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "Events:"), lfix);
  anaStaPad->AddFrame(fEvAnedLabel = new TGLabel(anaStaPad, "0"), lfix);
  fEvAnedLabel->SetTextFont("-adobe-courier-bold-r-*-*-14-*-*-*-*-*-iso8859-1");
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "  "), lfix); // just a padding
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "  "), lfix); // just a padding
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "  "), lfix); // just a padding
  // the ana results table - for tracking and PID
  // the second row: the table head
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "  "), lfix); // just a padding
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "X"), lfix);
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "Y(U)"), lfix);
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "V"), lfix);
  anaStaPad->AddFrame(new TGLabel(anaStaPad, "3D"), lfix);
  // the third row -> the sixth row: the table content //
  const char *thead[5] = {"Trk_DCTaU", "Trk_DCTaD", "Trk_postMagDC", "Trk_Global",
    "PID_postMag"};
  for(int i = 0; i < 5; i++){
    anaStaPad->AddFrame(new TGLabel(anaStaPad, thead[i]), lfix);
    for(int j = 0; j < 4; j++){
      bool skip = false;
      if(i < 2 && 2 == j) skip = true; // DCTa has no V
      if(3 == i && j > 0) skip = true; // Global only has X
      if(4 == i && j > 1) skip = true; // PID only use the first 2 boxes
      if(skip){
        anaStaPad->AddFrame(new TGLabel(anaStaPad, "  "), lfix); // just a padding
        continue;
      } // end if
      anaStaPad->AddFrame(fNofTrkL[i][j] = new TGLabel(anaStaPad, "0"), lfix);
      fNofTrkL[i][j]->SetTextFont("-adobe-courier-bold-r-*-*-14-*-*-*-*-*-iso8859-1");
    } // end for over j
  } // end for over i
} // end member function Initialize

void ETFGanaPre::AddRawDataFile(){
  static const char *filetypes[] = {
    "All files",   "*",
    "PXI files",   "*_*.dat",
    "VME files",   "*.[0-9][0-9][0-9]",
    "ROOT files",  "*.root",
    0,                  0
  };

  TGFileInfo fi;
  fi.fFileTypes = filetypes;
  fi.SetIniDir(".");
  new TGFileDialog(gClient->GetRoot(), fGana, kFDOpen, &fi);

  // append the filename to the fileNameLabel //
  if(nullptr == fi.fFilename){
    // cout << "Input file selection canceled" << endl;
    return;
  } // end if
  printf("Input file: %s", fi.fFilename);
  if(access(fi.fFilename, F_OK)){
    cout << fi.fFilename << " does not exist" << endl;
    return;
  } // end if
  printf(", size: %.4f MB\n", ETFMath::filesizeInMB(fi.fFilename));
  auto &vDataFile = fpm->fDataFiles;
  vDataFile.push_back(fi.fFilename);
  if(vDataFile.size() == 1) fFile0Size = ETFMath::filesize(fi.fFilename);

  // show in the GUI //
  string txt = "Input Data Files:";
  for(auto p : vDataFile) txt += '\n' + p;
  fDataFileLabel->SetText(txt.data());
  fDataFileLabel->Resize();
  fi.SetIniDir(".");
} // end member function AddRawDataFile

void ETFGanaPre::StartAna(){
  if(0 == fpm->fDataFiles.size()){
    new ETFGPrompt(gClient->GetRoot(), fGana, "Data file list is EMPTY");
    return;
  } // end if

  if(2 == fAnaSta) return; // has anaed
  // collect the user-input via GUI //
  unsigned &idx0 = fpm->fIndex0, &idx1 = fpm->fIndex1;
  idx0 = static_cast<unsigned>(atoi(fEntryEv0->GetText()));
  idx1 = static_cast<unsigned>(atoi(fEntryEv1->GetText()));
  if(idx1 <= idx0)
    er("ETFGanaPre", "StartAna: illegal [ev_start~ev_stop): [%d~%d)", idx0, idx1);
  fNofEv = idx1 - idx0;
  bool c3dtrk = fC3DTrk->GetState(), cpid = fCPID->GetState();
  if(c3dtrk || cpid) fC2DTrk->SetState(kButtonDown);
  bool c2dtrk = fC2DTrk->GetState();
  if(c2dtrk){
    if(cpid) fpm->fAnaLevel = 2;
    else fpm->fAnaLevel = 1;
    fpm->fIs3DTrk = c3dtrk;
  } // end if
  else{
    fpm->fAnaLevel = 0;
    fpm->fIs3DTrk = false;
  } // end else
  fAnaLevel = fpm->fAnaLevel;

  Disable(); // disable (wait) the editables since ana has started
  fGana->ActivateStopButton();
  fAnaSta = 1;
  ana();
  fAnaSta = 2;
  fGana->ActivatePostAna(); // post-ana ana system now can be activated
} // end member function StartAna

void ETFGanaPre::ana(){
  fRun->Init(); // init everything
  if(fAnaLevel >= 1) ftm = ETFTrackManager::Instance();
  if(fAnaLevel >= 2) fpidm = ETFPIDManager::Instance();
  fGana->SetROOTFile(fRun->GetRootFileName());
  fSrc = fRun->GetSource();

  fRun->Run(fpm->ev0(), fpm->ev1());
} // end member function ana

// disable all till the ana completed
void ETFGanaPre::Disable(){
  fStartAnaBtn->SetEnabled(false);
  fEntryEv0->SetEnabled(false);
  fEntryEv1->SetEnabled(false);
  fC2DTrk->SetEnabled(false);
  fC3DTrk->SetEnabled(false);
  fCPID->SetEnabled(false);
  fAddfBtn->SetEnabled(false);
} // end member function Disable

// update ana results
void ETFGanaPre::Update(){
  const int cntEv = fRun->CntEv();
  fEvAnedLabel->SetText(Form("%d", cntEv));
  fEvAnedLabel->Resize();
  // analysis in progress
  double prg = 0.;
  if(fNofEv < UINT_MAX - 10){ // user-specified ev-range
    prg = double(cntEv) / fNofEv;
  } // end if
  else{ // just deduce from size (in byte) analysized
    static ETFUnpacker *upk = fSrc->upks()[0];
    prg = double(upk->SizeCnt()) / fFile0Size;
    if(prg >= 0.999) prg = 1.;
  } // end else
  fPrgBar->SetPosition(prg * 100.);

  // tracking results //
  if(0 == fAnaLevel) return; // no tracking
  ETFTracker *trkr[4] = {ftm->GetPreTaTracker(), ftm->GetPostTaTracker(),
    ftm->GetPostMagTracker(), ftm->GetGlobalTracker()};
  TrackType tp[3] = {TrackType::kX, TrackType::kU, TrackType::kV};
  for(int i = 0; i < 4; i++){
    if(!trkr[i]) continue;
    for(int j = 0; j < 4; j++){
      auto p = fNofTrkL[i][j];
      if(!p) continue;
      const int nt =
        j < 3 ? trkr[i]->GetNofTracks(tp[j]) : trkr[i]->GetNof3DTracks();
      if(nt){
        fCnt[i][j] += nt;
        p->SetText(form("%d", fCnt[i][j]));
        p->Resize();
      } // end if
    } // end for over j
  } // end for over i
  if(fAnaLevel < 2) return; // no PID
  const int np = fpidm->GetPID()->FlySuccess();
  if(np){
    fCnt[4][0] += np;
    fNofTrkL[4][0]->SetText(std::to_string(fCnt[4][0]).data());
    fNofTrkL[4][0]->Resize();
    fNofTrkL[4][1]->SetText(form("%.1f%%\nhave PID", 100. * fCnt[4][0] / cntEv));
    fNofTrkL[4][1]->Resize();
  } // end if
} // end member function Update
